Package org.python.pydev.editor.codecompletion

Source Code of org.python.pydev.editor.codecompletion.PyLinkedModeCompletionProposal

/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on Jul 1, 2006
* @author Fabio
*/
package org.python.pydev.editor.codecompletion;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedModeUI;
import org.eclipse.jface.text.link.LinkedPositionGroup;
import org.eclipse.jface.text.link.ProposalPosition;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
import org.python.pydev.core.IToken;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.editor.hover.PyTextHover;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.FunctionDef;

import com.aptana.shared_core.string.FastStringBuffer;
import com.aptana.shared_core.utils.RunInUiThread;

public final class PyLinkedModeCompletionProposal extends AbstractPyCompletionProposalExtension2 implements
        ICompletionProposalExtension {

    private int firstParameterLen = 0;

    /**
     * The number of positions that we should add to the original position.
     *
     * Used so that when we enter '.', we add an additional position (because '.' will be added when applying the completion)
     * or so that we can go back one position when the toggle mode (ctrl) is on and a completion with parameters is applied (and they
     * are removed)
     */
    private int nPositionsAdded = 0;

    /**
     * If true, we'll go to the linked mode after applying a completion.
     */
    private boolean goToLinkedMode = true;

    /**
     * This is the token from where we should get the image and additional info.
     */
    private IToken element;

    /**
     * Offset forced to be returned (only valid if >= 0)
     */
    private int newForcedOffset = -1;

    /**
     * Constructor where the image and the docstring are lazily computed (initially added for the java integration).
     */
    public PyLinkedModeCompletionProposal(String replacementString, int replacementOffset, int replacementLength,
            int cursorPosition, IToken element, String displayString, IContextInformation contextInformation,
            int priority, int onApplyAction, String args) {

        super(replacementString, replacementOffset, replacementLength, cursorPosition, null, displayString,
                contextInformation, "", priority, onApplyAction, args);

        this.element = element;
    }

    /**
     * @return the element
     */
    public IToken getElement() {
        return element;
    }

    /**
     * Constructor where all the info is passed.
     */
    public PyLinkedModeCompletionProposal(String replacementString, int replacementOffset, int replacementLength,
            int cursorPosition, Image image, String displayString, IContextInformation contextInformation,
            String additionalProposalInfo, int priority, int onApplyAction, String args) {
        super(replacementString, replacementOffset, replacementLength, cursorPosition, image, displayString,
                contextInformation, additionalProposalInfo, priority, onApplyAction, args);
    }

    /**
     * Constructor where all the info is passed.
     */
    public PyLinkedModeCompletionProposal(String replacementString, int replacementOffset, int replacementLength,
            int cursorPosition, Image image, String displayString, IContextInformation contextInformation,
            String additionalProposalInfo, int priority, int onApplyAction, String args, boolean goToLinkedMode) {
        super(replacementString, replacementOffset, replacementLength, cursorPosition, image, displayString,
                contextInformation, additionalProposalInfo, priority, onApplyAction, args);
        this.goToLinkedMode = goToLinkedMode;
    }

    //methods overriden to be lazily gotten ----------------------------------------------------------------------------
    @Override
    public Image getImage() {
        if (element != null) {
            return element.getImage();
        } else {
            return super.getImage();
        }
    }

    private String computedInfo = null;

    @Override
    public String getAdditionalProposalInfo() {
        if (computedInfo != null) {
            return computedInfo;
        }

        if (element != null) {
            if (element instanceof SourceToken) {
                SourceToken sourceToken = (SourceToken) element;
                SimpleNode ast = sourceToken.getAst();
                if (ast != null && (ast instanceof FunctionDef || ast instanceof ClassDef)) {
                    computedInfo = PyTextHover.printAst(null, ast);
                }
                if (computedInfo != null) {
                    return computedInfo;
                }

            }
            computedInfo = element.getDocStr();
            return computedInfo;
        } else {
            return super.getAdditionalProposalInfo();
        }
    }

    //end methods overriden to be lazily gotten ------------------------------------------------------------------------

    /*
     * @see ICompletionProposal#getSelection(IDocument)
     */
    public Point getSelection(IDocument document) {
        if (newForcedOffset >= 0) {
            return new Point(newForcedOffset, 0);
        }

        if (onApplyAction == ON_APPLY_JUST_SHOW_CTX_INFO) {
            return null;
        }
        if (onApplyAction == ON_APPLY_SHOW_CTX_INFO_AND_ADD_PARAMETETRS) {
            if (fArgs.length() > 0) {
                return new Point(fReplacementOffset + fCursorPosition - 1, firstParameterLen); //the difference is the firstParameterLen here (instead of 0)
            }
            return null;
        }
        if (onApplyAction == ON_APPLY_DEFAULT) {
            return new Point(fReplacementOffset + fCursorPosition + nPositionsAdded, firstParameterLen); //the difference is the firstParameterLen here (instead of 0)
        }
        throw new RuntimeException("Unexpected apply mode:" + onApplyAction);
    }

    public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {

        boolean eat = (stateMask & SWT.MOD1) != 0;
        IDocument doc = viewer.getDocument();

        if (!triggerCharAppliesCurrentCompletion(trigger, doc, offset)) {
            newForcedOffset = offset + 1; //+1 because that's the len of the trigger
            return;
        }

        if (onApplyAction == ON_APPLY_JUST_SHOW_CTX_INFO) {
            return;
        }
        if (onApplyAction == ON_APPLY_SHOW_CTX_INFO_AND_ADD_PARAMETETRS) {
            try {
                String args;
                if (fArgs.length() > 0) {
                    args = fArgs.substring(1, fArgs.length() - 1); //remove the parenthesis
                } else {
                    args = "";
                }
                super.apply(doc);

                if (!goToLinkedMode) {
                    return;
                }

                int iPar = -1;
                int exitPos = offset + args.length() + 1;
                goToLinkedModeFromArgs(viewer, offset, doc, exitPos, iPar, args);

            } catch (BadLocationException e) {
                Log.log(e);
            }
            return;
        }

        if (onApplyAction == ON_APPLY_DEFAULT) {
            try {
                int dif = offset - fReplacementOffset;
                String strToAdd = fReplacementString.substring(dif);
                boolean doReturn = applyOnDoc(offset, eat, doc, dif, trigger);

                if (doReturn || !goToLinkedMode) {
                    return;
                }

                //ok, now, on to the linking part
                int iPar = strToAdd.indexOf('(');
                if (iPar != -1 && strToAdd.charAt(strToAdd.length() - 1) == ')') {
                    String newStr = strToAdd.substring(iPar + 1, strToAdd.length() - 1);
                    goToLinkedModeFromArgs(viewer, offset, doc, offset + strToAdd.length() + nPositionsAdded, iPar,
                            newStr);
                }
            } catch (BadLocationException e) {
                Log.log(e);
            }
            return;
        }

        throw new RuntimeException("Unexpected apply mode:" + onApplyAction);

    }

    /**
     * Applies the changes in the document (useful for testing)
     *
     * @param offset the offset where the change should be applied
     * @param eat whether we should 'eat' the selection (on toggle)
     * @param doc the document where the changes should be applied
     * @param dif the difference between the offset and and the replacement offset 
     *
     * @return whether we should return (and not keep on with the linking mode)
     * @throws BadLocationException
     */
    public boolean applyOnDoc(int offset, boolean eat, IDocument doc, int dif, char trigger)
            throws BadLocationException {
        boolean doReturn = false;

        String rep = fReplacementString;
        int iPar = rep.indexOf('(');

        if (eat) {

            //behavior change: when we have a parenthesis and we're in toggle (eat) mode, let's not add the
            //parenthesis anymore.
            if (/*fLastIsPar &&*/iPar != -1) {
                rep = rep.substring(0, iPar);
                doc.replace(offset - dif, dif + this.fLen, rep);
                //if the last was a parenthesis, there's nothing to link, so, let's return
                if (!fLastIsPar) {
                    nPositionsAdded = -1;
                }
                doReturn = true;
            } else {
                int sumReplace = 0;
                if (rep.endsWith("=")) {
                    //Special case when applying keyword completions (i.e.: call(param=10)), where the text added is "param=")
                    try {
                        char c = doc.getChar(offset + this.fLen);
                        if (c == '=') {
                            sumReplace++;
                        }
                    } catch (BadLocationException e) {
                        //just ignore it!
                    }
                }
                doc.replace(offset - dif, dif + this.fLen + sumReplace, rep);
            }
        } else {
            if (trigger == '.' || trigger == '(') {
                if (iPar != -1) {
                    //if we had a completion with parameters, we should just remove everything that would appear after
                    //the parenthesis -- and we don't need to raise nTriggerCharsAdded because the cursor position would
                    //already be after the '(. -- which in this case we'll substitute for a '.'
                    rep = rep.substring(0, iPar);
                } else {
                    nPositionsAdded = 1;
                }
                rep = rep + trigger;

                if (trigger == '(') {
                    rep += ')';
                }

                //linking should not happen when applying '.'
                doReturn = true;
            }

            //if the trigger is ')', just let it apply regularly -- so, ')' will only be added if it's already in the completion.
            doc.replace(offset - dif, dif, rep);
        }
        return doReturn;
    }

    private void goToLinkedModeFromArgs(ITextViewer viewer, int offset, IDocument doc, int exitPos, int iPar,
            String newStr) throws BadLocationException {
        if (!goToLinkedMode) {
            return;
        }
        List<Integer> offsetsAndLens = new ArrayList<Integer>();

        FastStringBuffer buffer = new FastStringBuffer();
        for (int i = 0; i < newStr.length(); i++) {
            char c = newStr.charAt(i);

            if (Character.isJavaIdentifierPart(c)) {
                if (buffer.length() == 0) {
                    offsetsAndLens.add(i);
                    buffer.append(c);
                } else {
                    buffer.append(c);
                }
            } else {
                if (buffer.length() > 0) {
                    offsetsAndLens.add(buffer.length());
                    buffer.clear();
                }
            }
        }
        if (buffer.length() > 0) {
            offsetsAndLens.add(buffer.length());
        }
        buffer = null;

        goToLinkedMode(viewer, offset, doc, exitPos, iPar, offsetsAndLens);
    }

    private void goToLinkedMode(ITextViewer viewer, int offset, IDocument doc, int exitPos, int iPar,
            List<Integer> offsetsAndLens) throws BadLocationException {
        if (!goToLinkedMode) {
            return;
        }
        if (offsetsAndLens.size() > 0) {
            LinkedModeModel model = new LinkedModeModel();

            for (int i = 0; i < offsetsAndLens.size(); i++) {
                Integer offs = offsetsAndLens.get(i);
                i++;
                Integer len = offsetsAndLens.get(i);
                if (i == 1) {
                    firstParameterLen = len;
                }
                int location = offset + iPar + offs + 1;
                LinkedPositionGroup group = new LinkedPositionGroup();
                ProposalPosition proposalPosition = new ProposalPosition(doc, location, len, 0,
                        new ICompletionProposal[0]);
                group.addPosition(proposalPosition);
                model.addGroup(group);
            }

            model.forceInstall();

            final LinkedModeUI ui = new EditorLinkedModeUI(model, viewer);
            ui.setDoContextInfo(true); //set it to request the ctx info from the completion processor
            ui.setExitPosition(viewer, exitPos, 0, Integer.MAX_VALUE);
            Runnable r = new Runnable() {
                public void run() {
                    ui.enter();
                }
            };
            RunInUiThread.async(r);

        }
    }

    //-------------------------------------------- ICompletionProposalExtension

    /**
     * We want to apply it on \n or on '.'
     *
     * When . is entered, the user will finish (and apply) the current completion
     * and request a new one with '.'
     *
     * If not added, it won't request the new one (and will just stop the current)
     */
    public char[] getTriggerCharacters() {
        if (onApplyAction != ON_APPLY_DEFAULT) {
            return null;
        }
        return super.getTriggerCharacters();
    }

    @Override
    public String toString() {
        return "PyLinkedModeCompletionProposal(" + this.getDisplayString() + ")";
    }

    //testing
    void setLen(int i) {
        this.fLen = i;
    }

}
TOP

Related Classes of org.python.pydev.editor.codecompletion.PyLinkedModeCompletionProposal

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.